//
//  CShader.cpp
//  OpenGLDemo
//
//  Created by 石超军 on 2018/7/6.
//  Copyright © 2018年 石超军. All rights reserved.
//
//#include <glad/glad.h>
#include <OpenGL/gl3.h>
#include "CShader.h"
#include <fstream>
#include <sstream>
#include <iostream>

CShader::CShader(const char* vertexPath, const char* fragmentPath, const char* geometryPath)
:__ID(0)
{
    // 1. retrieve the vertex/fragment source code from filePath
    std::string vertexCode;
    std::string fragmentCode;
    std::string geometryCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    std::ifstream gShaderFile;
    // ensure ifstream objects can throw exceptions:
    vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    gShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    try
    {
        // open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // convert stream into string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
        // if geometry shader path is present, also load a geometry shader
        if(geometryPath != nullptr)
        {
            gShaderFile.open(geometryPath);
            std::stringstream gShaderStream;
            gShaderStream << gShaderFile.rdbuf();
            gShaderFile.close();
            geometryCode = gShaderStream.str();
        }
    }
    catch (std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();
    // 2. compile shaders
    unsigned int vertex, fragment;

    // vertex shader
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    CheckCompileErrors(vertex, "VERTEX");
    // fragment Shader
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    CheckCompileErrors(fragment, "FRAGMENT");
    // if geometry shader is given, compile geometry shader
    unsigned int geometry = 0;
    if(geometryPath != nullptr)
    {
        const char * gShaderCode = geometryCode.c_str();
        geometry = glCreateShader(GL_GEOMETRY_SHADER);
        glShaderSource(geometry, 1, &gShaderCode, NULL);
        glCompileShader(geometry);
        CheckCompileErrors(geometry, "GEOMETRY");
    }
    // shader Program
    __ID = glCreateProgram();
    glAttachShader(__ID, vertex);
    glAttachShader(__ID, fragment);
    if(geometryPath != nullptr)
        glAttachShader(__ID, geometry);
    glLinkProgram(__ID);
    CheckCompileErrors(__ID, "PROGRAM");
    // delete the shaders as they're linked into our program now and no longer necessery
    glDeleteShader(vertex);
    glDeleteShader(fragment);
    if(geometryPath != nullptr)
        glDeleteShader(geometry);
    
}
CShader::~CShader()
{
    glDeleteProgram(__ID);
}

// activate the shader
// ------------------------------------------------------------------------
void CShader::Use()
{
    glUseProgram(__ID);
}
// utility uniform functions
// ------------------------------------------------------------------------
void CShader::SetBool(const std::string &name, bool value) const
{
    glUniform1i(glGetUniformLocation(__ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void CShader::SetInt(const std::string &name, int value) const
{
    glUniform1i(glGetUniformLocation(__ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void CShader::SetFloat(const std::string &name, float value) const
{
    glUniform1f(glGetUniformLocation(__ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void CShader::SetVec2(const std::string &name, const glm::vec2 &value) const
{
    glUniform2fv(glGetUniformLocation(__ID, name.c_str()), 1, &value[0]);
}
void CShader::SetVec2(const std::string &name, float x, float y) const
{
    glUniform2f(glGetUniformLocation(__ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void CShader::SetVec3(const std::string &name, const glm::vec3 &value) const
{
    glUniform3fv(glGetUniformLocation(__ID, name.c_str()), 1, &value[0]);
}
void CShader::SetVec3(const std::string &name, float x, float y, float z) const
{
    glUniform3f(glGetUniformLocation(__ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void CShader::SetVec4(const std::string &name, const glm::vec4 &value) const
{
    glUniform4fv(glGetUniformLocation(__ID, name.c_str()), 1, &value[0]);
}
void CShader::SetVec4(const std::string &name, float x, float y, float z, float w)
{
    glUniform4f(glGetUniformLocation(__ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void CShader::SetMat2(const std::string &name, const glm::mat2 &mat) const
{
    glUniformMatrix2fv(glGetUniformLocation(__ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void CShader::SetMat3(const std::string &name, const glm::mat3 &mat) const
{
    glUniformMatrix3fv(glGetUniformLocation(__ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void CShader::SetMat4(const std::string &name, const glm::mat4 &mat) const
{
    glUniformMatrix4fv(glGetUniformLocation(__ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

int CShader::GetInt(const std::string &name) const
{
    return glGetUniformLocation(__ID, name.c_str());
}

unsigned int CShader::GetId()
{
    return __ID;
}
// utility function for checking shader compilation/linking errors.
// ------------------------------------------------------------------------
void CShader::CheckCompileErrors(GLuint shader, std::string type)
{
    GLint success;
    GLchar infoLog[1024];
    if(type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if(!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if(!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
        }
    }
}
